/*
 * $Id: AbstractControl.java 153 2009-05-19 15:56:35Z meyer@alphatec.de $
 * 
 *
 * Copyright 2009 DeltaDesk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package deltadesk.gui.deltagui.widget;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

import deltadesk.gui.deltagui.data.Nameable;
import deltadesk.gui.deltagui.executor.ExecutorServices;
import deltadesk.gui.deltagui.focus.Focusable;
import deltadesk.gui.deltagui.style.Style;
import deltadesk.gui.deltagui.style.Styleable;


/**
 * Control.
 * 
 * @author Andreas Meyer
 * @version $Revision: 153 $
 */
public abstract class AbstractControl implements Control, Styleable, Nameable,
		Focusable {

	/** The bounds of this control. */
	private final Rectangle bounds;

	/** The preferred size of this control. */
	private final Dimension preferredSize;

	/** The style of this control. */
	private final Style style;

	/** The name of the control. */
	private String name;

	/** The parent composite of this control. */
	private Composite parent;

	/** The flag if the control needs to be rendered. */
	protected AtomicBoolean rendered = new AtomicBoolean(false);

	/** The flag if the control is opaque. */
	protected AtomicBoolean opaque = new AtomicBoolean(true);

	/** The flag if the control is focusable. */
	protected AtomicBoolean focusable = new AtomicBoolean(false);

	/** Thread pool executor for event notification. */
	private static final ExecutorService executor = ExecutorServices
			.getComponentExecutor();

	/** The focused control. */
	static AbstractControl focusedControl;


	/**
	 * Construct the control.
	 * 
	 */
	AbstractControl() {
		this(null);
	}
	
	/**
	 * Construct the control.
	 * 
	 * @param layout
	 * @param name
	 */
	AbstractControl(final String name) {

		this.name = name;

		// set defaults
		bounds = new Rectangle(0, 0, 0, 0);
		preferredSize = new Dimension(0, 0);
		style = Style.createDefault();
		setPreferredSize(64, 64);
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getX()
	 */
	public int getX() {
		return bounds.x;
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getY()
	 */
	public int getY() {
		return bounds.y;
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getHeight()
	 */
	public int getHeight() {
		return bounds.height;
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getWidth()
	 */
	public int getWidth() {
		return bounds.width;
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#setLocation(int, int)
	 */
	public final void setLocation(final int x, final int y) {

		if(bounds.x != x || bounds.y != y) {

			bounds.x  = x;
			bounds.y = y;
			executor.execute(onMove);
		} // end if
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#setSize(int, int)
	 */
	public final void setSize(final int width, final int height) {
		
		if(bounds.width != width || bounds.height != height) {

			bounds.width  = width;
			bounds.height = height;
			executor.execute(onResize);
		} // end if
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getPreferredWidth()
	 */
	public final int getPreferredWidth() {
		return preferredSize.width;
	}

	/**
	 * @see deltadesk.gui.deltagui.layout.Layoutable#getPreferredHeight()
	 */
	public final int getPreferredHeight() {
		return preferredSize.height;
	}

	/**
	 * Set the preferred size.
	 * 
	 * @param preferredWidth
	 * @param preferredHeight
	 */
	public final void setPreferredSize(final int preferredWidth,
			                     final int preferredHeight) {
		
		preferredSize.width  = preferredWidth;
		preferredSize.height = preferredHeight;
	}

	/**
	 * @see deltadesk.gui.deltagui.style.Styleable#getStyle()
	 */
	public Style getStyle() {
		return style;
	}

	/**
	 * @see deltadesk.gui.deltagui.data.Nameable#getName()
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param name the name to set
	 */
	protected final void setName(final String name) {
		this.name = name;
	}

	/**
	 * @return the parent
	 */
	public final Composite getParent() {
		return parent;
	}

	/**
	 * @see deltadesk.gui.deltagui.widget.Control#setParent(deltadesk.gui.deltagui.widget.Composite)
	 */
	public final void setParent(final Composite parent) {
		
		if(this.parent != parent) {

			this.parent = parent;
			if(parent != null) {
				executor.execute(onActivate);
			} else {
				executor.execute(onDeactivate);
			}
		} // end if
	}

	/**
	 * Invalidate the control.
	 */
	protected void invalidate() {
		if(parent != null) {
			parent.invalidate();
		}
	}

	/**
	 * Mark the control as rendered.
	 * 
	 */
	protected final void setRendered() {
		this.rendered.set(true);
	}

	/**
	 * @see deltadesk.gui.deltagui.graphics.Renderable#setUnrendered()
	 */
	public void setUnrendered() {
		this.rendered.set(false);
		if(getParent() != null && !opaque.get()) {
			getParent().rendered.set(false);
		}
	}
	
	/**
	 * Set the opaque flag.
	 * 
	 * @param opaque
	 */
	public void setOpaque(final boolean opaque) {
		this.opaque.set(opaque);
	}

	/**
	 * @return the opaque
	 */
	public boolean isOpaque() {
		return opaque.get();
	}

	/**
	 * @see deltadesk.gui.deltagui.graphics.Renderable#render(java.awt.Graphics2D)
	 */
	public void render(final Graphics2D g2) {

		if(!rendered.get()) {

			final Rectangle r = g2.getClipBounds();
			final Style style = getStyle();
			final Color bgColor = style.getBackgroundColor();
			if(opaque.get() && bgColor != null) {

				g2.setBackground(bgColor);
				g2.clearRect(r.x, r.y, r.width, r.height);
			} // end if

			// prepare graphics context and render specialized control
			g2.setColor(style.getForegroundColor());
			g2.setFont(getStyle().getFont());
			renderControl(g2);

			setRendered();
		} // end if (not rendered)
	}
	
	/**
	 * Render the control.
	 * 
	 * @param g2
	 */
	protected void renderControl(final Graphics2D g2) {
		// Sub classes override this to render the controls
	}

	/**
	 * Post a key event.
	 * 
	 * @param ke
	 */
	final void postKeyEvent(final KeyEvent ke) {
		onKeyEvent(ke);
	}
	
	/**
	 * @see deltadesk.gui.deltagui.focus.Focusable#isFocusable()
	 */
	public boolean isFocusable() {
		return focusable.get();
	}

	/**
	 * @see deltadesk.gui.deltagui.focus.Focusable#setFocusable(boolean)
	 */
	public void setFocusable(boolean focusable) {
		this.focusable.set(focusable);
	}

	/**
	 * @see deltadesk.gui.deltagui.focus.Focusable#hasFocus()
	 */
	public boolean hasFocus() {
		return focusedControl == this;
	}

	/**
	 * Transfer focus to this control.
	 */
	void setFocus() {
		focusedControl = this;
	}

	/** Reusable threaded onResize event. */
	private OnResize onResize = new OnResize();
	private class OnResize implements Runnable {
		public void run() { onResize(); }
	}

	/** Event for component resize. */
	protected abstract void onResize();

	/** Reusable threaded onMove event. */
	private OnMove onMove = new OnMove();
	private class OnMove implements Runnable {
		public void run() { onMove(); }
	}
	
	/** Event for component move. */
	protected abstract void onMove();

	/** Reusable threaded onActivate event. */
	private OnActivate onActivate = new OnActivate();
	private class OnActivate implements Runnable {
		public void run() { onActivate(); }
	}

	/** Event for component activate. */
	protected abstract void onActivate();

	/** Reusable threaded onDeactivate event. */
	private OnDeactivate onDeactivate = new OnDeactivate();
	private class OnDeactivate implements Runnable {
		public void run() { onDeactivate(); }
	}

	/** Event for component deactivate. */
	protected abstract void onDeactivate();

	/** Event for key event. */
	protected abstract void onKeyEvent(KeyEvent ke);

	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		
		final StringBuffer sb = new  StringBuffer();
		
		sb.append(getClass().getSimpleName());
		sb.append("[");
		sb.append(name);
		sb.append("] Location=");
		sb.append(getX());
		sb.append(",");
		sb.append(getY());
		sb.append(" Size=");
		sb.append(getWidth());
		sb.append(",");
		sb.append(getHeight());
	
		return sb.toString();
	}

}
